Amplify Gen1 チュートリアルアプリケーションを手動で Amplify Gen2 に移行してみた
いわさです。
最近 Amplify Gen2 をよく使っているのですが、先日 Amplify Gen1 で構築したアプリケーションを Gen2 へ移行出来るか検討する機会がありました。
現時点での Gen1 から Gen2 への移行に関する公式ドキュメントのメッセージは以下です。
We are actively developing migration tooling to aid in transitioning your project from Gen 1 to Gen 2. Until then, we recommend you continue working with your Gen 1 Amplify project.
要は「移行ツール開発中なので、それが出来るまでは Gen1 を使い続けてください。まだしばらく Gen1 もサポートし続けるよ。」と言われています。
公式が移行ツールを予定しているのであれば待ちたいところですが、GA 前から移行ツール開発中のままアナウンスがないのと、ビジネス上の理由から Gen2 に早く移行したい場合もあると思います。そうした時に上記記載内容からいずれ Gen1 のサポートが終了して Gen2 のみのサポートになりそうな雰囲気も感じるので、タイミングによっては移行ツールを待たずに移行したいケースが出てきそうです。
本日は Amplify Gen1 から Gen2 に手動で移行するために何が必要になるのかを確認しつつ、次のチュートリアルアプリケーションの移行をまずはやってみましたので紹介します。
Amplify Gen1 と Gen2 の違い
Amplify と言っても複数の要素が存在しています。Amplify アプリケーションをホスティングする環境だったり、クライアントライブリ & Amplify UI、バックエンドなどです。
ご存知の方も多いと思いますが、まずいちばん大きく変わるのはバックエンドの定義方法です。
Gen1 では Amplify CLI を使ってリソースの定義を行ってamplify push
する方法でした。
一方で Gen2 では TypeScript の AWS CDK L3 コントラクトを使って Amplify バックエンドリソースを定義し、そこからサンドボックスや各環境にデプロイする方法です。
認証に Cognito、データに AppSync + DynamoDB などのバックエンド基本構成は同じなのですが、管理方法・デプロイ方法などが全く違います。
また、ホスティング環境についても若干異なっています。
AWS 上の Amplify コンソールを見ていただくとわかりやすいですが Gen1 と Gen2 でアプリケーションが分かれています。そして Gen2 に関しては GitHub などの git プロバイダー経由で自動でデプロイパイプラインが動作するような動きになっています。
本記事ではまずはアプリケーションへの影響に焦点を当てたく、ビルド・ホスティング環境についてはまた別に記事で取り上げたいと頂いて、上記バックエンドの定義周りを中心に検証をしてみたいと思います。
Amplify.configure の互換
Gen1 も Gen2 もバックエンドリソースの定義方法は異なりますが、生成されたバックエンドリソースとフロントエンドコードを関連付けるために Gen1 でも Gen2 でもAmplify.configure
を初期化処理として実装する点は同じです。
Gen1 の場合はamplifyconfiguration.json
あるいはaws-exports.js
、Gen2 の場合はamplify_outputs.json
を読み込ませるのですが、Gen1 と Gen2 で構成ファイルのフォーマットは大きく異なります。
ただし、確認したところこのモジュールは Gen1 と Gen2 の構成ファイルどちらもサポートされているようでした。
なのでフロントエンド実装の観点のみで言うと、ここは Gen1 でも Gen2 でもどちらでもコードの変更は不要そうです。読み込む対象ファイルだけ変更してやれば良さそうです。
認証を変換させてみる
で、上記前提でまずは認証部分を変更してみます。
Gen1 のチュートリアルどおりに進めると以下のあたりで認証バックエンドを統合する形になるはずです。
Gen1 ではamplify add auth
することで Cognito ユーザープールと関連付けされます。
あとは Authenticator コンポーネントを使ってやることでうまいこと定義した Congnito ユーザープールを使ってサインインやサインアップが出来るようになります。
Gen1 での定義と実装
上記のとおりバックエンド定義を追加します。
iwasa.takahito@HL01200 hoge1018amplify % amplify add auth
Using service: Cognito, provided by: awscloudformation
The current configured provider is Amazon Cognito.
Do you want to use the default authentication and security configuration? Default configuration
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? Email
Do you want to configure advanced settings? No, I am done.
✅ Successfully added auth resource hoge1018amplify2637ba75 locally
✅ Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
iwasa.takahito@HL01200 hoge1018amplify % amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
┌──────────┬─────────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin │
├──────────┼─────────────────────────┼───────────┼───────────────────┤
│ Auth │ hoge1018amplify2637ba75 │ Create │ awscloudformation │
└──────────┴─────────────────────────┴───────────┴───────────────────┘
✔ Are you sure you want to continue? (Y/n) · yes
Deployment completed.
Deploying root stack hoge1018amplify [ ====================-------------------- ] 1/2
amplify-hoge1018amplify-dev-f… AWS::CloudFormation::Stack UPDATE_IN_PROGRESS Fri Oct 18 2024 1
authhoge1018amplify2637ba75 AWS::CloudFormation::Stack CREATE_COMPLETE Fri Oct 18 2024 1
Deployed auth hoge1018amplify2637ba75 [ ======================================== ] 6/6
UserPool AWS::Cognito::UserPool CREATE_COMPLETE Fri Oct 18 2024 1
UserPoolClientRole AWS::IAM::Role CREATE_IN_PROGRESS Fri Oct 18 2024 1
UserPoolClient AWS::Cognito::UserPoolClient CREATE_COMPLETE Fri Oct 18 2024 1
UserPoolClientWeb AWS::Cognito::UserPoolClient CREATE_COMPLETE Fri Oct 18 2024 1
IdentityPool AWS::Cognito::IdentityPool CREATE_COMPLETE Fri Oct 18 2024 1
IdentityPoolRoleMap AWS::Cognito::IdentityPoolRol… CREATE_COMPLETE Fri Oct 18 2024 1
Deployment state saved successfully.
これで Cognito User Pool が作成されるし、Amplify の構成ファイルにもユーザープール情報が含まれる
設定ファイルが色々と追加され、amplifyconfiguration.json
にユーザープール情報などが追加されるようになりました。
Gen1 定義から Cognito ユーザープールが作成されています。
Authenticator コンポーネントからフロントエンドにアクセスしてみると見慣れたサインイン画面が。
新規サインアップしてみると上記ユーザープール上に新しいユーザーが作成されました。
Gen2 定義に置き換えてみる
続いてここに Gen2 の手動インストールを行ってみます。
まず、Gen1 導入環境にそのまま Gen2 の手動インストールを試してみました。
すると、Gen1 の導入によって既に amplify フォルダが存在しているため、Gen2 の手動セットアップに失敗します。
iwasa.takahito@HL01200 hoge1021amplify % npm create amplify@latest
> hoge1021amplify@0.0.0 npx
> create-amplify
? Where should we create your project? .
Error: An amplify directory already exists at /Users/iwasa.takahito/work/hoge1021gen1togen2/hoge1021amplify/amplify. If you are trying to run an Amplify Gen 2 command inside an Amplify Gen 1 project we recommend creating the project in another directory. Learn more about AWS Amplify Gen 2: https://docs.amplify.aws/gen2/how-amplify-works/
npm error code 1
npm error path /Users/iwasa.takahito/work/hoge1021gen1togen2/hoge1021amplify
npm error command failed
npm error command sh -c create-amplify
npm error A complete log of this run can be found in: /Users/iwasa.takahito/.npm/_logs/2024-10-20T20_49_39_184Z-debug-0.log
そこで、Gen1 フォルダをリネームして初期導入することにしました。
本格的に Gen2 に移行するとなるとどうせ amplify フォルダ配下は作り直しになるでしょうし、おそらく開発中の移行ツールも amplify 配下のフォルダを各 Gen1 の定義ファイルから TypeScript コードを生成してくれる感じなのではないかなと予想。
以下のような感じにしました。
Gen2 デフォルト設定で Auth 定義がされていますので、認証周りの Gen1 の設定を auth の resource.ts で踏襲する必要があります。
今回はデフォルトの E メール & MFA なしで良さそうな感じなので、このまま行ってみましょう。
ここでサンドボックスをデプロイしました。
これでクラウド上に Gen2 定義の AWS リソースが作成され、ローカルにamplify_outputs.json
という構成ファイルが作成されました。冒頭言及したとおり Amplify.configure でこちらを読み込むように変更しておきましょう。
実行し Gen1 で作成したユーザー情報でサインインしようとすると失敗します。
新しくサインアップしてみるとサインインができます。
コンソールを確認してみると新しいユーザープールが作成されており、Gen1 と Gen2 で別々のユーザープールが参照されていることがわかります。
認証用に画面で Authenticator を使っている場合はフロントエンド側の修正なしで動作させることができました。
Auth の新しい定義を既存を踏襲してやり、Amplify.configure の構成ファイルを変更することで動作するようです。
データを変換させてみる
続いて、データ部分も対応してみます。
データは Gen1 の場合だとadd api
で、GraphQL と REST から選択が出来るのですが、今回は GraphQL を選択した前提で対応をしてみます。GraphQL を選択した場合は Gen1 も Gen2 も AppSync + DynamoDB が基本構成となります。
Gen1 での定義と実装
Gen1 では上記ドキュメントに従ってデータ API を追加してプッシュすると GraphQL スキーマ定義などがローカルに作成されます。
チュートリアルアプリケーションでは、こちらをフロントエンドから参照しています。
サンプルの ToDo アプリが動作します。
フォームに Name と Description を入力し、ボタンを押すとデータベースにデータが登録されます。
その実体としてはデータソースに DynamoDB を使う AppSync です。
マネジメントコンソールの API 一覧を確認してみると Amplify によってデプロイされた AppSync リソースを確認することが出来るはずです。
また、データソースの DynamoDB テーブルを確認してみると、先程作成された項目を確認することもできました。
Gen2 定義に置き換えてみる
こちらも Gen2 に変換してみます。
前提として先ほどと同様に Gen1 の amplify フォルダをリネームし、Gen2 の手動セットアップで Gen2 フォルダを追加しています。デフォルトで Auth と Data が含まれていまして、データのリソース定義を少し修正します。
具体的には、Gen2 デフォルトと Gen1 デフォルトの属性がちょっと違うので、Gen2 側の定義を Gen1 に合わせてやる必要があります。content を削除し、name と description を文字列として追加しました。
で、フロントエンド側もaws-amplify/api
のgenerateClient
を使っていたので、aws-amplify/data
のgenerateClient
やスキーマ定義を参照するように修正してやりました。
import { useEffect, useState } from 'react';
// import { generateClient } from 'aws-amplify/api';
import { generateClient } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource';
// import { createTodo } from './graphql/mutations';
// import { listTodos } from './graphql/queries';
// import { type CreateTodoInput, type Todo } from './API';
// const initialState: CreateTodoInput = { name: '', description: '' };
const initialState = { name: '', description: '' };
// const client = generateClient();
const client = generateClient<Schema>();
const App = () => {
//const [formState, setFormState] = useState<CreateTodoInput>(initialState);
const [formState, setFormState] = useState(initialState);
// const [todos, setTodos] = useState<Todo[] | CreateTodoInput[]>([]);
const [todos, setTodos] = useState<Schema["Todo"]["type"][]>([]);
useEffect(() => {
fetchTodos();
}, []);
async function fetchTodos() {
try {
// const todoData = await client.graphql({
// query: listTodos,
// });
// const todos = todoData.data.listTodos.items;
const { data: todos } = await client.models.Todo.list();
setTodos(todos);
} catch (err) {
console.log('error fetching todos');
}
}
async function addTodo() {
try {
if (!formState.name || !formState.description) return;
const todo = { ...formState };
// setTodos([...todos, todo]);
setFormState(initialState);
// await client.graphql({
// query: createTodo,
// variables: {
// input: todo,
// },
// });
await client.models.Todo.create(todo);
} catch (err) {
console.log('error creating todo:', err);
}
}
return (
<div style={styles.container}>
<h2>Amplify Todos</h2>
<input
onChange={(event) =>
setFormState({ ...formState, name: event.target.value })
}
style={styles.input}
value={formState.name}
placeholder="Name"
/>
<input
onChange={(event) =>
setFormState({ ...formState, description: event.target.value })
}
style={styles.input}
value={formState.description as string}
placeholder="Description"
/>
<button style={styles.button} onClick={addTodo}>
Create Todo
</button>
{todos.map((todo, index) => (
<div key={todo.id ? todo.id : index} style={styles.todo}>
<p style={styles.todoName}>{todo.name}</p>
<p style={styles.todoDescription}>{todo.description}</p>
</div>
))}
</div>
);
};
:
実行してみると新しいデータが登録出来るようになりました。
Gen1 と同じ感じです。
登録先の確認は認証と同じなのですが、構成ファイルから AppSync を特定し対象のデータソースからテーブル内容を確認してみます。
Gen1 と Gen2 の AppSycn と DynamoDB テーブルが分かれており、Gen2 テーブルに新しいデータが登録・参照出来るようになっていることが確認できました。
フロントエンド側でどのようにバックエンドと対話しているか次第ですが、Authenticator を使っている部分だけを比較した認証と比べると少しデータは変更量が多かったですね。
さいごに
本日は Amplify Gen1 チュートリアルアプリケーションを手動で Amplify Gen2 に移行してみました。
まずはチュートリアルで出てくると認証とデータ部分の対応を行ってみました。
amplify フォルダについては Gen2 を手動インストールして新規作成し、クライアント側は一部修正を行いました。この感じだと Gen1 の使い方次第で修正量は変わりそうなので全然参考にならない気もしますが、移行ツールが出てくるのであれば可能であれば待ったほうが良さそうな気もしますね。